package util.ui;

/************************************************************
 * Contains code for main table
 * and code for outputting to file from menu in ContigListTab
 */
import javax.swing.*;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.awt.*;

import javax.swing.table.TableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.event.ListSelectionEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.IOException;

import assem.Utils;
import util.Debug.ErrorReport;
import util.methods.Converters;
import jpave.dataholders.MainData;
import jpave.query_interface.JPaveFrame;
import jpave.dataholders.SequenceData;

public class SortableTable extends JTable implements TableModel
{	
	public SortableTable ( 	ColumnwiseIndexer inIndexer, 
							Vector<MainData> inRows, 
							int[] inDefaultFields,
							final ActionListener refreshListener)
	throws Exception
	{	
		rows = inRows;
		indexer = inIndexer;
		visibleColumns = inDefaultFields;
		visibleRows = rows;
		
		computeAverages(); 
		
		// Setup the table
		setAutoCreateColumnsFromModel( true );
		setColumnSelectionAllowed( false );
		setCellSelectionEnabled( false );
		setRowSelectionAllowed( true );
		setShowHorizontalLines( false );
		setShowVerticalLines( true );	
		setIntercellSpacing ( new Dimension ( 1, 0 ) );
		setModel ( this );
		
		colListener = new ColumnListener(this, visibleColumns.length);
		bAscend = new boolean[visibleColumns.length];
		for(int x=0; x<bAscend.length; x++)
			bAscend[x] = true;
		
		getColumnModel().addColumnModelListener(colListener);
				
		JTableHeader header = getTableHeader();
		
		header.setReorderingAllowed( true );
		header.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
			
				if (e.getClickCount() != 1)
					return;
				
				int[] savedColumnWidths = getColumnWidths(); // kludge to prevent column auto-resizing on sort
				
				tableHeaderClicked(e);
				setColumnWidths(savedColumnWidths); 
			}
		});
		headerRenderer.setRenderer( new DefaultTableCellRenderer () );
		header.setDefaultRenderer( headerRenderer );
	
		addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2 
						&& getSelectedRowCount() == 1
						&& isRowSelected(rowAtPoint(e.getPoint()))
						&& rowAtPoint(e.getPoint()) < getRowCount()) // mdb added 1/14/08
					notifyDoubleClick();
			}
		});

		setAutoResizeMode(AUTO_RESIZE_OFF);
		autofitColumns();
	}
	
	protected int getMappedColumn(int columnIndex, boolean debug) {
		return colListener.getFieldPositionFromColumn(columnIndex);
	}
	
	protected int getReverseMappedColumn(int columnIndex) {
		return colListener.getReverseFieldPositionFromColumn(columnIndex);
	}
	
	protected boolean hasColumnMoved(int columnIndex) {
		return colListener.hasColumnMoved(columnIndex);
	}
	
	private static final int MAX_AUTOFIT_COLUMN_WIDTH = 90; // in pixels
    protected void autofitColumns() {
        TableModel model = getModel();
        TableColumn column;
        Component comp;
        int headerWidth;
        int cellWidth;
        TableCellRenderer headerRenderer = getTableHeader().getDefaultRenderer();
        
        for (int i = 0;  i < getModel().getColumnCount();  i++) { // for each column
            column = getColumnModel().getColumn(i);

            comp = headerRenderer.getTableCellRendererComponent(
                                 this, column.getHeaderValue(),
                                 false, false, 0, i);
            
            headerWidth = comp.getPreferredSize().width + 10; // why +10 ???
            
            cellWidth = 0;
            for (int j = 0;  j < getModel().getRowCount();  j++) { // for each row
	            comp = getDefaultRenderer(model.getColumnClass(i)).
	                             getTableCellRendererComponent(
	                                 this, model.getValueAt(j, i),
	                                 false, false, j, i);
	            cellWidth = Math.max(cellWidth, comp.getPreferredSize().width);
	            if (j > 100) break; // only check beginning rows, for performance reasons
            }

            column.setPreferredWidth(Math.min(Math.max(headerWidth, cellWidth), MAX_AUTOFIT_COLUMN_WIDTH));
        }
    }
    
    private int[] getColumnWidths()
    {
	    	int numColumns = getModel().getColumnCount();
	    	int[] widths = new int[numColumns];
	    	for (int i = 0;  i < numColumns;  i++)
	    		widths[i] = getColumnModel().getColumn(i).getPreferredWidth();
	    	
	    	return widths;
    }
    
    private void setColumnWidths(int[] widths)
    {
	    	for (int i = 0;  i < getModel().getColumnCount();  i++)
	    		getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
    }
	
	private void computeAverages()
	{
		if (rows == null || rows.size() == 0) return;
		averages = new Object[indexer.getNumFields()];
		averages[0] = new String("Average");
		
		int totalRows = getVisibleRowCount();
		bAverages = (totalRows > 1);
		
		for (int i = 1;  i < getColumnCount();  i++) {
			long intSum = 0;
			double floatSum = 0;
			
			Object obj = null;
			for (int j = 0;  j < totalRows;  j++) {
				obj = getValueAt(j, i);
				
				if (obj instanceof DisplayInt)
					intSum += ((DisplayInt)obj).longValue();
				else if (obj instanceof DisplayFloat)
					floatSum += ((DisplayFloat)obj).getValue();
				else if (obj instanceof Integer)
					intSum += ((Integer)obj).intValue();
				else if (obj instanceof Long)
					intSum += ((Long)obj).longValue();
			}

			if (obj instanceof DisplayInt || obj instanceof Integer || obj instanceof Long)
				averages[i] = new DisplayFloat(intSum/totalRows);
			else if (obj instanceof DisplayFloat)
				averages[i] = Converters.decimalString(floatSum/totalRows, 2);
			else
				averages[i] = new String("");
		}
	}
	
    public void setVisibleColumns ( int [] indexes )
    {
    		clearSelection ();
    	
        if ( ntheSortCol >= 0 ) {
            // See if the sorted field is in the new columns
            int nSortField = getFieldIdxFromColumnIdx ( ntheSortCol );
            ntheSortCol = -1;
            for ( int i = 0; i < indexes.length; ++i ) {    
                if ( indexes[i] == nSortField ) {
                    ntheSortCol = i;
                    break;
                }
            }
        }
        visibleColumns = indexes;
        notifyTableRowsChanged ();    
        notifyHeaderChanged ();
    }
    
    public void setVisibleRows( Vector<MainData> newVisibleRows ) {
	    	if (newVisibleRows == null || newVisibleRows.size() == rows.size())
	    		visibleRows = rows;
	    	else
	    		visibleRows = newVisibleRows;
	    	
	    	computeAverages();
    	
    	// Update table graphic
		revalidate();
		repaint();
    }
    
	private void addMatches(Vector<MainData> matches, String strRegEx, int columnIdx) {
		Pattern p = Pattern.compile(strRegEx, Pattern.CASE_INSENSITIVE);
		Matcher m;
		Boolean isZeroLength = (strRegEx.length() == 0); // for speed, any improvement?
		String s2;
		
		for (int i = 0;  i < rows.size();  i++) {
			s2 = indexer.extractFieldByID ( rows.get( i ), getFieldIdxFromColumnIdx ( columnIdx ) ).toString();
			m = p.matcher(s2);
			if (isZeroLength || m.matches())
				matches.addElement(rows.get(i));
		}
	}
	
	protected static String strFilterCopy = null;
	public void filterRows(String strSearch, int columnIdx) {
		if (strSearch.length() == 0) {
			setVisibleRows(null); // make all rows visible
			return;
		}
		
		strSearch = strSearch.replaceAll("\\*", ".*");
		
		if (strSearch != strFilterCopy) {
			Vector<MainData> matches = new Vector<MainData>();
			
			addMatches(matches, strSearch, columnIdx);
			if (matches.size() == 0 // no matches
					&& !strSearch.startsWith(".*") 
					&& !strSearch.endsWith(".*"))
			{
				addMatches(matches, ".*"+strSearch+".*", columnIdx); // search again with wildcards at each end
			}
			
			setVisibleRows(matches);
			strFilterCopy = new String(strSearch);
		}
	}
	
	public void filterRows(String strSearch, int columnIdx1, int columnIdx2) {
		filterRows(strSearch, columnIdx1);
		Vector<MainData> visibleRowsCopy = visibleRows;
		filterRows(strSearch, columnIdx2);
		visibleRowsCopy.addAll(visibleRows);
		setVisibleRows(visibleRowsCopy);
	}
	
	public void setOnlySingleRowSelection ( )
	{
		ListSelectionModel model = new DefaultListSelectionModel ();
		model.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
		setSelectionModel( model );
	}
	
	protected void sortByColumn ( int nColumn, final boolean bInAscending )
	{
        final int nNewSortField = getFieldIdxFromColumnIdx ( nColumn );
        if ( nNewSortField < 0 )
            return;
        
		if ( !indexer.isFieldSortableByID( nNewSortField ) )
			return;
		
		// Save the new sort settings
		ntheSortCol = nColumn;
		
		Comparator<Object> sortRows = new Comparator<Object> () {
			public int compare ( Object row1, Object row2 ) 
	    	{
				Object o1 = convertNull ( indexer.extractFieldByID ( row1, nNewSortField ) );
	            Object o2 = convertNull ( indexer.extractFieldByID ( row2, nNewSortField ) );
	            
	            // Sort nulls to the beginning.  This seems fairly natural.  
	            // I tried at the end and it just didn't feel right.
				int nRes = 0;
	            if ( o1 == o2 ) 		nRes = 0;
	            else if ( o1 == null )	nRes = -1;
	            else if ( o2 == null )	nRes = 1;
	            else {
	                Comparable c1 = (Comparable)o1;
	                Comparable c2 = (Comparable)o2;
	                
	                nRes = c1.compareTo( c2 );
	            }
	            
				// Invert the result if this is descending
	    		if ( !bInAscending )
	    			nRes = -nRes;
	    		
	    		return nRes;
	    	}
		};
		
        // Sort
		if ( rows != null )
	        Collections.sort( rows, sortRows);
		
		if ( visibleRows != null )
	        Collections.sort( visibleRows, sortRows); // mdb added 5/29/08
		
        // Notify listeners:
        notifyHeaderChanged ();
        notifyTableRowsChanged ();
        
        // Send event for selection changed
        tableSelectionChanged ();
	}
	
	public void setSelectedRows(int start, int end)
	{
		setRowSelectionInterval(start, end);
	}
	
	public void addDoubleClickListener(ActionListener l)
	{
		if ( !doubleClickListeners.contains( l ) )
			doubleClickListeners.add ( l ); 		
	}
	
	public void addSelectionChangeListener(ActionListener l) 
	{ 
		if ( !actionListeners.contains( l ) )
			actionListeners.add ( l ); 
	};
	
	public void valueChanged(ListSelectionEvent e)
	{
		// Note: the if statement solves a chick-egg problem 
		// in constructor with JRE 1.5.0  This gets called before the columns
		// are populated causing an array to go out of bounds in the column model.
		if ( getColumnModel().getColumnCount() > 0 )
		{
			super.valueChanged(e); 
			tableSelectionChanged ( );
		}
	}
	
	public void columnSelectionChanged(ListSelectionEvent e)
	{
		super.columnSelectionChanged(e); 
		tableSelectionChanged ( );
	}
	
	protected void notifyHeaderChanged ( )
	{
	    // Notify listeners:
        TableModelEvent eventColumnHeader = new TableModelEvent ( this, TableModelEvent.HEADER_ROW );
        TableModelListener curListener;
        
        for ( int i = 0; i < tableModelListeners.size(); ++i ) {
        	curListener = (TableModelListener)tableModelListeners.get(i);
        	curListener.tableChanged(eventColumnHeader);
        }
    }
	
	protected void notifyTableRowsChanged ( )
	{
	    // Notify listeners:
        TableModelEvent eventAllRows = new TableModelEvent ( this );
        TableModelListener curListener;
        
        for ( int i = 0; i < tableModelListeners.size(); ++i ) {
	        	curListener = (TableModelListener)tableModelListeners.get(i);
	        	curListener.tableChanged(eventAllRows);
        }		
	}
	
	private void notifyDoubleClick ( )
	{		
	    ActionEvent e = new ActionEvent ( this, ActionEvent.ACTION_PERFORMED, "DoubleClickSingleRow" );

	    for ( int i = 0; i < doubleClickListeners.size(); ++i ) {
	        	ActionListener curListener = (ActionListener)doubleClickListeners.get(i);
	        	curListener.actionPerformed( e );
        }				
	}
	
	protected void tableSelectionChanged ( )
	{
        if ( actionListeners == null ) return;
		
		// Notify listeners:
        ActionEvent e = new ActionEvent ( this, ActionEvent.ACTION_PERFORMED, "SelectionChanged" );
        ActionListener curListener;
        
        for ( int i = 0; i < actionListeners.size(); ++i ) {
	        	curListener = (ActionListener)actionListeners.get(i);
	        	curListener.actionPerformed( e );
        }		
	}
	
	private void tableHeaderClicked( MouseEvent e ) 
	{ 
		if (e.getClickCount() == 1) {
			JTableHeader h = null;
			try {
				// Figure out what column was clicked:
				h = (JTableHeader)e.getSource();
				h.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 
		        TableColumnModel columnModel = h.getColumnModel();
		  
		        String columnName = this.getColumnName(getMappedColumn(this.getColumnModel().getColumnIndexAtX(e.getX()), false));
		        int viewColumn = getMappedColumn(columnModel.getColumnIndexAtX(e.getX() + 1), false) - 1;
		        viewColumn = getColumnModel().getColumnIndex(columnName);
	            if ( viewColumn < 0 )
	                return;   
	            
		        int nNewColumn = columnModel.getColumn(viewColumn).getModelIndex()-1;
		        if (nNewColumn < 0 ) return;
		        
		        if(SwingUtilities.isLeftMouseButton(e))
		        	bAscend[nNewColumn] = !bAscend[nNewColumn];
		        else
		        	bAscend[nNewColumn] = true;
		        
		        sortByColumn ( nNewColumn, bAscend[nNewColumn] );    
			}
			catch ( Exception err ) {
				ErrorReport.reportError(err, "Internal error: sorting table");
			}
			finally { // ensure that cursor is restored
				if (h != null) 
					h.setCursor(Cursor.getDefaultCursor());
			}
		}
	}
	
	public TableCellRenderer getCellRenderer(int row, int column) 
	{
		rendererWrapper.setRenderer( super.getCellRenderer ( row, column ) );
		return rendererWrapper;
	}
	
	private class SortableCellRenderer implements TableCellRenderer
	{
		void setRenderer ( TableCellRenderer inRenderer )
		{
			renderer = inRenderer;
		}
		
		public Component getTableCellRendererComponent (JTable table,
														Object value,
														boolean isSelected,
										                boolean hasFocus,
										                int row,
										                int column )
		{
			boolean bHeader = row == -1;
			boolean bBlueBG = row % 2 == 1;
						
			JLabel label = (JLabel)renderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );				
			if ( bHeader ) {
				label.setBorder( BorderFactory.createEtchedBorder() );
				label.setBackground ( JPaveFrame.getComponentColor() );
			}
			else {
				label.setBorder( null );
			
				if ( isSelected )
					label.setBackground ( JPaveFrame.getSelectionColor() );
				else {
					if ( bBlueBG )
						label.setBackground( JPaveFrame.getAltRowColor() );
					else
						label.setBackground( null );
				}
			}
			label.setFont( theFont );
			return label;
		}		
		
		TableCellRenderer renderer = null;
	};
	
	public int getEntryCount()
	{
		if (rows == null)
			return 0;
		
		return rows.size();
	}
	
	/************** Implementation of TableModel interface *******************/
	public Class<?> getColumnClass(int columnIndex) 
	{ 
		try {
			return Class.forName( "java.lang.String" ); 
		}
		catch ( Exception err ) {
			return null;
		}
	}
  
	public int getColumnCount() 
	{ 
		if ( indexer == null )
			return 0;
        else if ( visibleColumns != null )
            return visibleColumns.length + 1;	
		else
			return indexer.getNumFields() + 1;	
	};
  
    protected int getFieldIdxFromColumnIdx ( int columnIndex )
    {
        if ( columnIndex < 0 )
            return columnIndex;
        
        if ( visibleColumns != null && columnIndex<visibleColumns.length)
            return visibleColumns [ columnIndex ];     
        else
            return columnIndex;
    }
    
	public String getColumnName( int columnIndex )
	{
		if ( indexer == null )
			return "";
		else {
			if (columnIndex-- == 0) return "Row #"; 
			
			String strReturn = indexer.getFieldNameByID( getFieldIdxFromColumnIdx ( columnIndex ) ); 
					
			return strReturn;
		}
	}
	
	public int getVisibleRowCount() // doesn't include averages row
	{
		if (visibleRows == null)
			return 0;
			
		return visibleRows.size();
	}
  
	public int getRowCount() 
	{ 
		if ( indexer == null || rows == null || rows.size() == 0 )
			return 0;
		else
			return getVisibleRowCount() + (bAverages ? 1 : 0);
	};
	
	public Object getValueAt( int rowIndex, int columnIndex )
	{		
		if (  indexer == null || rows == null || rowIndex > rows.size() || rows.size() == 0)
			return null;
		else if (bAverages && (rowIndex >= getVisibleRowCount())) // mdb added case 1/14/08
			return averages[columnIndex];
		else {	
			if (columnIndex-- == 0)  return rowIndex+1; 
			
			// Pass the indicated row's object to the indexer to extract the desired field
            Object obj = indexer.extractFieldByID (visibleRows.get( rowIndex ), 
            		getFieldIdxFromColumnIdx ( columnIndex ) );
            obj = convertNull ( obj );
			return obj;
		}
	}
	
	public Object getRowAt(int nRow) 
	{
		return visibleRows.get( nRow );
	}
	
	public int getSelectedRow()
	{
		int nRow = super.getSelectedRow();
		
		if (rows == null) return nRow; // for CachedTable
		
		if (bAverages && nRow >= getVisibleRowCount()) // abort if "averages" row
			return -1;
		
		// Get record number since row number depends on what's visible
		Object record = indexer.extractFieldByID ( visibleRows.get( nRow ), getFieldIdxFromColumnIdx ( 0 ) ); 
		
		// Based on record number get row number in full list
		int i;
		for (i = 0;  i < rows.size();  i++) {
			if (record == indexer.extractFieldByID ( rows.get( i ), getFieldIdxFromColumnIdx ( 0 ) ))
				break;
		}
		
		return i;
	}
	
	public int[] getSelectedRows()
	{
		int[] nRows = super.getSelectedRows();
		
		if (rows == null) return nRows; // for CachedTable
		
		// Get record number since row number depends on what's visible
		Object[] records = new Object[nRows.length];
		for (int i = 0;  i < nRows.length;  i++)
			records[i] = indexer.extractFieldByID ( visibleRows.get( nRows[i] ), getFieldIdxFromColumnIdx ( 0 ) ); 
		
		// Based on record number get row number in full list
		int[] nRows2 = new int[nRows.length];
		for (int i = 0;  i < rows.size();  i++) {
			for (int j = 0;  j < records.length;  j++) {
				if (records[j] == indexer.extractFieldByID ( rows.get( i ), getFieldIdxFromColumnIdx ( 0 ) ))
					nRows2[j] = i;
			}
		}
		
		return nRows2;
	}
	
	public void setValueAt(Object aValue, int rowIndex, int columnIndex)  { super.setValueAt(aValue, rowIndex, columnIndex); } // never called
	
	public boolean isCellEditable(int rowIndex, int columnIndex) { return false; }
	
	public void removeTableModelListener(TableModelListener l) 
	{
		tableModelListeners.remove( tableModelListeners );
	}

	public void addTableModelListener(TableModelListener l) 
	{ 
		if ( !tableModelListeners.contains( l ) )
				tableModelListeners.add ( l ); 
	}
    
    protected Object convertNull ( Object obj )
    {
        // Look for min/max int/double.  I tend to use these for null with simple types
        if ( obj instanceof Integer )
        {
            Integer val = (Integer)obj;
            if ( val.intValue() == Integer.MIN_VALUE )
                obj = null;
        }
        else if ( obj instanceof Float )
        {
            Float val = (Float)obj;
            if ( Math.abs( val.floatValue() ) == Float.MAX_VALUE )
                obj = null;
        }
        else if ( obj instanceof Double )
        {
            Double val = (Double)obj;
            if ( Math.abs( val.doubleValue() ) == Double.MAX_VALUE )
                obj = null;
        }
        return obj;
    }
    
    public String copyTableToString() {
    		StringBuilder target = new StringBuilder();
    		try {
            // skip row# column
            for(int x=1; x < getColumnCount()-1; x++) {
                //Remove spaces from column headers
                target.append(((String)getColumnModel().getColumn(x).getHeaderValue()).replaceAll("\\s", ""));
                target.append("\t");
            }
            target.append(((String)getColumnModel().getColumn(getColumnCount()-1).getHeaderValue()).replaceAll("\\s", ""));
            target.append("\n");
            
            // skip last row
            for (int row = 0;  row < getRowCount()-1;  row++) {
                for (int col = 1;  col < getColumnCount();  col++) {
                    Object obj = getValueAt(row, col);
                    String s = (obj == null ? "" : obj.toString()); 
                    // surrounds ',' with quotes
                    if(s.indexOf(',') >= 0)
                        s = "\"" + s + "\"";
                    
                    target.append(s);
                    if(col + 1 < getColumnCount())
                        target.append('\t');
                }
                target.append("\n");
            }
        }
        catch (Exception err) {
            ErrorReport.reportError(err, "Internal error copying table");
        }
        return target.toString();
    }
    /******************************************************************
     * BEGIN EXPORT CODE
     * These are here because they read from the table -- this table
     **************************************************************/
    
    public int saveToFileTabDelim(File f) {
        PrintWriter pw = null;
    
        try {
            System.err.println("Begin writing file " +  f.getPath());
            pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
            
            String val = "";
            // skip row# column
            for(int x=1; x < getColumnCount()-1; x++) {
                //Remove spaces from column headers
                val = ((String)getColumnModel().getColumn(x).getHeaderValue()).replaceAll("\\s", "");
                pw.print(val);
                pw.print('\t');
            }
            val = ((String)getColumnModel().getColumn(getColumnCount()-1).getHeaderValue()).replaceAll("\\s", "");
            pw.println(val);
            
            // skip last row
            for (int row = 0;  row < getRowCount()-1;  row++) {
                if(row % 100 == 0)
                    System.err.print("   Wrote " + row + " rows" + "...\r");
                for (int col = 1;  col < getColumnCount();  col++) {
                    Object obj = getValueAt(row, col);
                    String s = (obj == null ? "" : obj.toString()); // check for empty string
                    // surrounds ',' with quotes
                    if(s.indexOf(',') >= 0)
                        s = "\"" + s + "\"";
                    
                    pw.print(s);
                    if(col + 1 < getColumnCount())
                        pw.print('\t');
                }
                pw.println();
            }
            System.err.println("   Wrote " + (getRowCount()-1) + " records ");
        }
        catch (IOException err) {
            ErrorReport.reportError(err, "Internal error: writing file");
        }
        finally {
            pw.close();
        } 
        return 0;
    }
    /***************************************************
     * save sequences to fasta file
     * get seqID (contigid) from interface table and read sequence from database
     */
    public int saveToFasta(File f, Connection conn) {
        PrintWriter pw = null;
       
        try {
            System.err.println("Being writing sequences to " + f.getPath());
            System.err.println("   Note: only the sequences from the table are written.");
                
            pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
            
            int contigIDIdx = getColumnModel().getColumnIndex("Seq ID");
            Statement stmt = conn.createStatement();
    
            String query = "SELECT consensus FROM contig WHERE contigid = '";
                
            for (int row = 0;  row < getRowCount()-1;  row++) {
                if(row % 100 == 0)
                    System.err.print("   Wrote " + row + " rows...\r");
                int i=0;
                Object obj=null;
                while (obj==null && i<10) {
                		obj = getValueAt(row, contigIDIdx);
                		i++;
                }
                if (obj==null) {
                		System.err.println("Cannot get row " + row);
                		continue;
                }
                String ctg = obj.toString(); 
                
                ResultSet rset = stmt.executeQuery(query + ctg + "'");
                rset.next();
                String sequence = rset.getString(1);
                pw.println(">" + ctg);
                pw.println(sequence);
                rset.close();
            }
            stmt.close();
            pw.close();
            System.err.println("   Wrote " + (getRowCount()-1) + " records ");
        }
        catch (Exception err) {
            ErrorReport.reportError(err, "Internal error: exporting  table to " + f.getPath());
        }
        finally {
            pw.close();
            System.err.println("Finish writing fasta file " + f.getName() + "              ");
        }       
        return 0;
    }
    /*******************************************************
     * save ORF to file
     * this wasn't working on MAC, getting null values
     */
    public int saveORFToFasta(File f, Connection conn) {
        PrintWriter pw = null;
        String query="";
        try {
            System.err.println("Begin writing translated coding sequences to " + f.getPath());
            System.err.println("   Note: only the sequences from the table with an ORF are written.");
                
            pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
            
            int contigIDIdx = getColumnModel().getColumnIndex("Seq ID");
            Statement stmt = conn.createStatement();
            int ignore=0, prt=0;
            query = "SELECT o_frame, o_coding_start, o_coding_end, consensus " +
            		           " FROM contig WHERE contigid = '";
            int rowCount = getRowCount()-1;
            for (int row = 0;  row < rowCount;  row++) {
                if(row % 100 == 0)
                    System.err.print("   Processed " + row + " rows...\r");
                
                int i=0;
                Object obj=null;
                while (obj==null && i<20) {
                		obj = getValueAt(row, contigIDIdx);
                		i++;
                }
                if (obj==null) {
                		System.err.println("Cannot get row " + row + " ");
                		System.err.println("Known problem on Mac -- working on fix");
                		System.err.println("Try again (limit size of table if possible)");
                		return 0;
                }
                String ctg = obj.toString(); 
            
                ResultSet rset = stmt.executeQuery(query + ctg + "'");
                if(rset.next()) {
                    int frame = rset.getInt(1);
                    if (frame == 0) {
                    		ignore++;
                    		continue; // no ORF
                    }
                    
                    int start = rset.getInt(2);
                    int end = rset.getInt(3);
     
                    String strSeq = rset.getString(4);
                    String orf = SequenceData.getORFtrans(ctg, strSeq, frame, start, end);
                	
                    pw.println(">" + ctg + " AAlen=" + orf.length() + " frame=" + frame);
                    pw.println(orf);
                    prt++;
                }
                rset.close();
            }
            stmt.close();
            pw.close();
            System.out.println("   Wrote " + prt + " records   Ignored " + ignore + " due to frame=0");
        }
        catch (Exception err) {
            ErrorReport.reportError(err, "Internal error: exporting  table\nQuery: " + query);
        }
        finally {
            pw.close();
            System.err.println("Finish writing fasta file " + f.getName() + "              ");
        }       
        return 0;
    }

    
    // Save the GOs from the table, for the given leve. For each one, save the # of contigs 
    // and the DE columns.
    public void saveToFileGO(File f, String level, Connection conn)
    {
    		try
    		{
    			System.err.println("Collecting GO information....");
	        int contigIDIdx = getColumnModel().getColumnIndex("Seq ID");
	        Statement stmt = conn.createStatement();
	        
	        TreeMap<Integer,Integer> goCounts = new TreeMap<Integer,Integer>();
		      
	        // collect the GOs from these contigs
	        for (int row = 0;  row < getRowCount()-1;  row++) 
	        {
	            if(getValueAt(row, contigIDIdx) != null) 
	            {
	                String ctg = getValueAt(row, contigIDIdx).toString(); 
	                
	                String query = " select gi.gonum from pja_unitrans_go as ug join contig as c " +
	                	" on c.ctgid=ug.ctgid join go_info as gi on gi.gonum=ug.gonum " + 
	                	" where c.contigid='" + ctg + "' ";
	                	if (!level.contains("-"))
	                	{
	                		query += " and gi.level=" + level;
	                	}
	                	else
	                	{
	                		String[] lvls = level.split("-");
	                		if (lvls.length >= 2 && !lvls[0].trim().equals("") && !lvls[1].trim().equals(""))
	                		{
	                			query += " and gi.level >= " + lvls[0] + " and gi.level <= " + lvls[1];
	                		}
	                		else
	                		{
	                			System.err.println("Invalid GO level entry " + level);
	                			return;
	                		}
	                	}
	                ResultSet r = stmt.executeQuery(query);
	                while (r.next())
	                {
		                	int gonum = r.getInt(1);
		                	if (!goCounts.containsKey(gonum))
		                	{
		                		goCounts.put(gonum, 1);
		                	}
		                	else
		                	{
		                		goCounts.put(gonum,1+goCounts.get(gonum));
		                	}
	                }
	                r.close();
	            }
	        }
	        // get the P__ columns
	        Vector<String> pcols = new Vector<String>();
	        ResultSet r = stmt.executeQuery("show columns from go_info like 'P__%'");
	        while (r.next())
	        {
	        		pcols.add(r.getString(1));
	        }
	        r.close();
	        
	        // get the go info and write the table
	        
	        if (f.exists()) f.delete();
	        BufferedWriter w = new BufferedWriter(new FileWriter(f));
	        System.out.println("Writing GOs to " + f.getPath()); // CAS 12/21/14
	        w.write("GO,description,level, term_type, #seqs");
	        if (pcols.size() >0)
	        {
		        	w.write(",");
		        	w.write(Utils.strVectorJoin(pcols, ","));
	        }
	        w.newLine();
	        int nrows = 0;
	        for (int gonum : goCounts.keySet())
	        {
		        	String query = "select descr, level, term_type ";
		        	if (pcols.size() > 0) query += "," + Utils.strVectorJoin(pcols, ",") ;
		        	query += " from go_info where gonum=" + gonum;
		        	r = stmt.executeQuery(query);
		        	if (r.first())
		        	{
		        		StringBuffer sb = new StringBuffer();
		        		sb.append("" + gonum);
		        		Vector<String> lvec = new Vector<String>();
		        		lvec.add("" + gonum);
		        		lvec.add(r.getString(1));
		        		lvec.add(r.getString(2));
		        		lvec.add(r.getString(3));
		        		lvec.add("" + goCounts.get(gonum));
		        		for (int i = 1; i <= pcols.size(); i++)
		        		{
		        			lvec.add(r.getString(3+i));
		        		}
		        		w.write(Utils.strVectorJoin(lvec, ",") );
		        		w.newLine();
		        		nrows++;
		        	}
		        	else
		        	{
		        		System.err.println("Failed to find GO:" + gonum);
		        	}
	        }
	        w.close();
	        System.err.println("Finished (wrote " + nrows + " rows)\n");
	    	}
	    	catch(Exception e){ ErrorReport.prtReport(e, "Writing GO file");}
    }
    /**
     * Write 2 files of PCC and 1 file of RPKM
     * the .sif is used by CytoScape
     */
    private double PCC_cutoff = 0.9;
    public void saveToFilePCC(File f, String pcc, String [] libraries) {
        
       	String pccFile = f.getAbsolutePath() + ".pcc"; 
        String sifFile = f.getAbsolutePath() + ".sif";
       	PCC_cutoff = Double.parseDouble(pcc);
       	
       	System.err.println("Writing .pcc, .sif for " + f.getAbsolutePath());
       	writePCC(pccFile, sifFile, libraries, false);
    }
    /* use base 2 */
    public void saveToFilePCClog2(File f, String pcc, String [] libraries) {
        String pccFile = f.getAbsolutePath() + ".lg2.pcc";
        String sifFile = f.getAbsolutePath() + ".lg2.sif"; 
        PCC_cutoff = Double.parseDouble(pcc);
        
        System.err.println("Writing .lg2.pcc, .lg2.sif for " + f.getAbsolutePath());
        writePCC(pccFile, sifFile, libraries, true);
    }
    private void writePCC(String pccFile, String sifFile, String [] libraries, boolean dolog2)
    {
    		System.err.println("Note: cutoff on PCC for .sif file = " + PCC_cutoff);
    		System.err.println("Note: only the sequences and RPKM columns from the table are used.");
    		
        	try {
        		PrintWriter pccPW = null, sifPW = null;
        		double log2 = Math.log(2); // log is base e (log10 is base 10)
        		/* Save column indexes from TCW table that contain normalized library names
        		 * Note: though in database it starts in LN__, that is removed for contig table
        		 * and libraries array has no prefixes
        		 */
           	 ArrayList <Integer> libCol = new ArrayList <Integer> ();
           	 for(int x=1; x < getColumnCount()-1; x++) {
                 String col = ((String)getColumnModel().getColumn(x).getHeaderValue());
                           
                 for (int i=0; i< libraries.length; i++) {
                     if (col.equals(libraries[i])) {
                         libCol.add(x);
                         break;
                     }
                 }
             }
      		int numLibs = libCol.size();
           	 if (numLibs == 0) {
           		 JOptionPane.showMessageDialog(null,"No normalized library columns in table. Select via Select Columns");
           		 System.err.println("Abort writing PCC files");
           	     return;
           	 }
           	 else {
           	     System.err.println("Found " + numLibs + " normalized columns in table");
           	 }
        
          // build list of ctgs and rpkm
       		int contigIDIdx = getColumnModel().getColumnIndex("Seq ID");
       		ArrayList <String> CtgRPKM = new ArrayList <String> (); // Name RPKM1:RPKM2....
       		
       		for (int row = 0;  row < getRowCount()-1;  row++) {
       		    if(row % 100 == 0)
       		        System.err.print("   Read " + row + " rows" + "...\r");
       		    String ctg = null, list="";
       		   
       		    ctg = getValueAt(row, contigIDIdx).toString();
       		 
       		    for (int i = 0;  i < libCol.size();  i++) {
       		       Object obj = getValueAt(row, libCol.get(i));
       		       if (dolog2) {
       		    	   		double rpkm = Double.valueOf(obj.toString()).doubleValue();
       		    	   		double l = Math.log(rpkm)/log2;
       		    	   		if (l < 0) l = 0;
       		    	   		list += ":" + l;  
       		       }
       		       else list += ":" + obj.toString();    
       		    }    
       		    CtgRPKM.add(ctg + list);
       		}
        		System.err.println("   Loaded " + CtgRPKM.size() + " rows            ");
        		
        		/***************** PCC with RPKM********************************/
           	
           	File f2 = new File (pccFile);
            File f3 = new File (sifFile);
        		pccPW = new PrintWriter(new BufferedWriter(new FileWriter(f2)));
        		sifPW = new PrintWriter(new BufferedWriter(new FileWriter(f3)));
        		Double [] x = new Double[numLibs];
        		Double [] y = new Double[numLibs];
        		
        		int cntP=0;
        		for (int i=0; i < CtgRPKM.size(); i++) {
        		    if (i % 10 == 0)
        		        System.err.print("   compute " + i + "th row, write " + cntP + " ....\r");
        			
        			String [] ctg1 = CtgRPKM.get(i).split(":");	
        			String xl="";
        			for (int t=1; t<ctg1.length; t++) { // ctg1 has seqID as element 0
        				x[t-1] = Double.valueOf(ctg1[t]);
        				xl += String.format("%.2f ", x[t-1]);
        			}      			
        			
        			for (int j=i+1; j < CtgRPKM.size(); j++) {	  			
            			String [] ctg2 = CtgRPKM.get(j).split(":");
            			String yl="";
            			for (int t=1; t<ctg2.length; t++) {
            				y[t-1] = Double.valueOf(ctg2[t]);
            				yl += String.format("%.2f ", y[t-1]);
            			}
            			
            			double pcc = GetCorrelation(x, y);
            			String p = String.format("%.4f", pcc);
            			pccPW.println(ctg1[0] + " " + ctg2[0] + " " + p + "   " + xl + "   " + yl);
            			
            			if (pcc >= PCC_cutoff) {
            				sifPW.println(ctg1[0] + " pp " + ctg2[0]);
            				cntP++;
            			}
        			}
        		}
       		System.err.println("Finish writing files, wrote " + cntP + " to " + sifFile);
       		pccPW.close(); sifPW.close();
        	}
        	catch (Exception err) {
        		ErrorReport.reportError(err, "Internal error: exporting PCC");
        	}   	
        	return;
    }
    
    // Pearson Correlation Coefficient: verified with on-line PCC calculators
    private static double GetCorrelation(Double [] xVect, Double [] yVect) {
        double sumX = 0.0, sumY = 0.0, sumXY = 0.0, powX=0.0, powY=0.0;
        double sz = xVect.length;
        try {
	        for(int i = 0; i < xVect.length; i++)
	        {
	            double x = xVect[i];
	            double y = yVect[i];
	            sumX  += x;
	            sumY  += y;
	            powX  += (x*x);
	            powY  += (y*y);
	            sumXY += (x*y);
	        }
	        
	        double num = sumXY - ((sumX*sumY)/sz);
	        
	        double demX = powX - ((sumX * sumX)/sz);
	        double demY = powY - ((sumY * sumY)/sz);
	        double dem = Math.sqrt(demX * demY);
	        double r = (num!=0 && dem!=0) ? (num/dem) : 0;
	        return r;
        }
        catch (Exception e) {
        		System.out.println(xVect.length + " :" +  Arrays.toString( xVect) );
        		System.out.println(yVect.length + " :" +  Arrays.toString( yVect) );
        		ErrorReport.reportError(e, "Internal error: exporting correlation");
        		return 0.0;
        }
      }//end: GetCorrelation(X,Y)


    /******************************************************
     * write file in format used by MEW, plus a dummy annotation file
     * note: may have problem with duplicate uniprotIDs
     */
    public int saveToFileMeV(File f, Connection conn, String [] libraries) {
        	PrintWriter expPW = null, annoPW = null;
        	String expFile = f.getAbsolutePath() + ".mev.exp";
        	String annoFile = f.getAbsolutePath() + ".mev.anno";
        	File fexp = new File (expFile);
        	File fanno = new File (annoFile);
        	try {
        		System.err.println("Begin writing files " + fexp.getPath() + " and " + fanno.getPath());
    
        		annoPW = new PrintWriter(new BufferedWriter(new FileWriter(fanno)));
           	annoPW.println("PROBE_ID\tCHR\tSTRAND\tTX_START\tTX_END\tCDS_START\tCDS_END\texonCount\texonStarts\texonEnds\tGENE_SYMBOL\tGENE_TITLE\tREFSEQ_ACC\tPROTEIN_ACC\tENTREZ_ID");
     
       		expPW = new PrintWriter(new BufferedWriter(new FileWriter(fexp)));
       		expPW.print("tracking_id\tlocus\tnearest_ref_id\tclass_code\ttranscript_length\t");
       		for(int x=0; x<libraries.length-1; x++) {
       			expPW.print(libraries[x]);
       			expPW.print('\t');
       		}
       		expPW.println(libraries[libraries.length-1]);
       		
       		int contigIDIdx = getColumnModel().getColumnIndex("Seq ID");
       		Statement stmt = conn.createStatement();
    
       		String query = "SELECT bestmatchid, consensus_bases";
        		for(int x=0; x<libraries.length; x++)
        			query += ", L__" + libraries[x];
        		query += " FROM contig WHERE contigid = '";
        		
        		for (int row = 0;  row < getRowCount()-1;  row++) {
        			if(row % 10 == 0)
        				System.err.print("   Wrote " + row + " rows...\r");
        			String ctg = getValueAt(row, contigIDIdx).toString();
        			
        			ResultSet rset = stmt.executeQuery(query + ctg + "'");
        			rset.next();
        			String hit = rset.getString(1); 
        			int len = rset.getInt(2);
        			if(hit==null)
        				hit = "EMPTY_NO_HIT";
        			
     			annoPW.println(hit + "\t\t-\t0\t0\t0\t0\t0\t0\t0\t\t\t\t\t");
     			
     	   		StringBuffer strBuf = new StringBuffer();	
     			strBuf.append(ctg + "\tchr0:0-" + len + "\t" + hit + "\t-\t");
     			
     			for(int x=0; x<libraries.length; x++) {
     				strBuf.append("\t" + rset.getInt(3 + x));
     			}
        			expPW.println(strBuf);
        			rset.close();
        		}
        		stmt.close();
        	}
        	catch (Exception err) {
        		ErrorReport.reportError(err,"Internal error: exporting  table" );
        	}
        	finally {
        		expPW.close();
        		annoPW.close();
        		System.err.println("Finish writing files " + fexp.getName() + " and " + fanno.getName());
        	}    	
        	return 0;
    }
 
	private Font theFont = JPaveFrame.getTextFont();
	private SortableCellRenderer rendererWrapper = new SortableCellRenderer ();
	private SortableCellRenderer headerRenderer = new SortableCellRenderer ();
	private ColumnwiseIndexer indexer = null;
	private ArrayList<TableModelListener> tableModelListeners = new ArrayList<TableModelListener> ();
	private ArrayList<ActionListener> actionListeners = new ArrayList<ActionListener> ();
	private ArrayList<ActionListener> doubleClickListeners = new ArrayList<ActionListener> ();
	private Vector<MainData> rows = null;
	protected Object[] averages = null; 	
    protected int[] visibleColumns = null;
    private Vector<MainData> visibleRows = null; 
    protected boolean [] colAscend = null;
	protected int ntheSortCol = -1;
	protected boolean [] bAscend = null;
	protected boolean bAverages = true; 	
  
    private static final long serialVersionUID = 1;
	private ColumnListener colListener = null;

	private class ColumnListener implements TableColumnModelListener {
	    	public ColumnListener(JTable table, int numColumns) {
	    		colMap = new int[numColumns+1];
	    		
	    		for(int x=0; x<=numColumns; x++)
	    			colMap[x] = x;
	    		
	    		theTable = table;
	    	}
    	
		public void columnMoved(TableColumnModelEvent e) {
		    int fromIndex = e.getFromIndex();
		    int toIndex = e.getToIndex();

		    if(fromIndex != toIndex)
		    	swap(fromIndex, toIndex);
		}
		
		public int getFieldPositionFromColumn(int column) {
			return colMap[column];
		}
		
		public boolean hasColumnMoved(int column) {
			return colMap[column] == column;
		}
		
		public int getReverseFieldPositionFromColumn(int column) {
			int retVal = -1;
			for(int x=0; x<colMap.length && retVal<0; x++)
				if(colMap[x] == column)
					retVal = x;
			return retVal;
		}

		public void columnAdded(TableColumnModelEvent arg0) {}
		public void columnMarginChanged(ChangeEvent arg0) {}
		public void columnRemoved(TableColumnModelEvent arg0) {}
		public void columnSelectionChanged(ListSelectionEvent arg0) {}
		private void swap(int x, int y) {
			colMap[x] ^= colMap[y];
			colMap[y] ^= colMap[x];
			colMap[x] ^= colMap[y];
		}
		
		private int [] colMap = null;
		private JTable theTable = null;
    }
}